package tech.aiflowy.common.filestorage.s3;


import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import tech.aiflowy.common.filestorage.StorageConfig;

import java.io.*;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Date;

/**
 * S3 存储协议 所有兼容S3协议的云厂商均支持 阿里云 腾讯云 七牛云 minio
 */
public class S3Client {
    private static final Logger log = LoggerFactory.getLogger(S3Client.class);
    /**
     * https 状态
     */
    private final String[] CLOUD_SERVICE = new String[]{"aliyun", "qcloud", "qiniu", "obs"};
    private static final int IS_HTTPS = 1;
    private final S3StorageConfig properties;

    private final AmazonS3 client;

    public S3Client() {
        this.properties = S3StorageConfig.getInstance();
        try {
            AwsClientBuilder.EndpointConfiguration endpointConfig =
                    new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());

            AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
            AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
            ClientConfiguration clientConfig = new ClientConfiguration();
            if (IS_HTTPS == properties.getIsHttps()) {
                clientConfig.setProtocol(Protocol.HTTPS);
            } else {
                clientConfig.setProtocol(Protocol.HTTP);
            }
            AmazonS3ClientBuilder build = AmazonS3Client.builder().withEndpointConfiguration(endpointConfig)
                    .withClientConfiguration(clientConfig).withCredentials(credentialsProvider).disableChunkedEncoding();
            if (isPathStyleAccessRequired(properties.getEndpoint())) {
                build.enablePathStyleAccess();
            }

            this.client = build.build();

            createBucket();
        } catch (Exception e) {
            throw new RuntimeException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
        }
    }

    private boolean isPathStyleAccessRequired(String endpoint) {
        return !StrUtil.containsAny(endpoint,
                CLOUD_SERVICE);
    }

    public void createBucket() {
        try {
            String bucketName = properties.getBucketName();
            if (client.doesBucketExistV2(bucketName)) {
                return;
            }
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            AccessPolicyType accessPolicy = getAccessPolicy();
            createBucketRequest.setCannedAcl(accessPolicy.getAcl());
            client.createBucket(createBucketRequest);
            client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
        } catch (Exception e) {
            throw new RuntimeException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
        }
    }

    public void upload(byte[] data, String objectPath, String contentType) {
        upload(new ByteArrayInputStream(data), objectPath, contentType);
    }

    public void upload(InputStream inputStream, String objectPath, String contentType) {
        if (!(inputStream instanceof ByteArrayInputStream)) {
            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
        }
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(contentType);
            metadata.setContentLength(inputStream.available());
            PutObjectRequest putObjectRequest =
                    new PutObjectRequest(properties.getBucketName(), objectPath, inputStream, metadata);
            // 设置上传对象的 Acl 为公共读
            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
            client.putObject(putObjectRequest);
        } catch (Exception e) {
            throw new RuntimeException("上传文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
    }

    public String upload(MultipartFile file) throws Exception {
        byte[] content = file.getBytes();
        String name = file.getOriginalFilename();
        String path = generatePath(content, name);
        String baseUrl = properties.getEndpoint() + "/" + properties.getBucketName() + "/";

        if (StringUtils.hasValue(properties.getPrefix())) {
            path = properties.getPrefix() + "/" + path;
        }
        String completeUrl = baseUrl + path;
        upload(content, path, file.getContentType());
        return completeUrl;
    }

    public static String generatePath(byte[] content, String originalName) throws Exception {
        // 计算文件内容的 SHA256 哈希值
        String sha256Hex = sha256Hex(content);

        // 情况一：如果存在原始文件名，优先使用其后缀
        if (StrUtil.isNotBlank(originalName)) {
            // 提取文件后缀
            String extName = FileNameUtil.extName(originalName);
            // 如果后缀存在，返回 "哈希值.后缀"，否则返回 "哈希值"
            return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
        }

        // 情况二：如果原始文件名为空，基于文件内容推断文件类型
        return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
    }

    public static String sha256Hex(byte[] input) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(input);

        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString(); // 返回字符串类型的哈希值
    }

    /**
     * 根据OSS对象存储路径, 从OSS存储删除文件
     *
     * @param objectPath 文件访问路径
     */
    public void delete(String objectPath) {
        try {
            client.deleteObject(properties.getBucketName(), objectPath);
        } catch (Exception e) {
            throw new RuntimeException("删除文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
    }


    /**
     * 根据OSS对象存储路径, 获取文件元数据
     *
     * @param objectPath 文件访问路径(对应OSS存储数据的key)
     */
    public ObjectMetadata getObjectMetadata(String objectPath) {
        return client.getObjectMetadata(properties.getBucketName(), objectPath);
    }

    /**
     * 根据OSS对象存储路径, 获取文件内容
     *
     * @param objectPath 文件访问路径
     * @return 文件内容
     */
    public InputStream getObjectContent(String objectPath) {
        return getObjectContent(objectPath, null, null);
    }

    /**
     * 根据OSS对象存储路径, 获取文件内容(指定文件字节范围)
     *
     * @param objectPath 文件访问路径
     * @param start      起始字节
     * @param end        结束字节
     * @return 文件内容(指定文件字节范围)
     */
    public InputStream getObjectContent(String objectPath, Long start, Long end) {
        GetObjectRequest request = new GetObjectRequest(properties.getBucketName(), objectPath);
        if (start != null) {
            if (end != null) {
                request.setRange(start, end);
            } else {
                request.setRange(start);
            }
        }
        S3Object object = client.getObject(request);
        return object.getObjectContent();
    }

    private String getSchema() {
        String schema = (IS_HTTPS == properties.getIsHttps() ? "https://" : "http://");
        return schema;
    }

    /**
     * 获取OSS基础路径
     *
     * @return 基础路径
     */
    public String getBasePath() {
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String schema = this.getSchema();
        // 云服务厂商object url格式: <schema>://<bucket>.<endpoint>/<objectPath>
        if (StrUtil.isNotBlank(domain)) {
            return schema + domain;
        }
        return schema + properties.getBucketName() + "." + endpoint;
    }

    /**
     * 根据访问url, 获取OSS对象存储路径
     *
     * @return OSS对象存储路径
     */
    public String getObjectPath(String url) {
        if (StrUtil.isBlank(url)) {
            return url;
        }
        return url.replace(getBasePath() + "/", "");
    }

    /**
     * 根据OSS对象存储路径, 获取访问url
     *
     * @param objectPath 文件访问路径
     * @return 访问url
     */
    public String getUrl(String objectPath) {
        return getBasePath() + "/" + objectPath;
    }

    public String getUrl() {
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String header = getSchema();
        // 云服务商直接返回
        if (StrUtil.isNotBlank(domain)) {
            return header + domain;
        }
        return header + properties.getBucketName() + "." + endpoint;
    }

    /**
     * 根据前缀和后缀, 生成OSS对象存储路径
     *
     * @param prefix 前缀
     * @param suffix 后缀
     * @return OSS对象存储路径
     */
    public String getObjectPath(String prefix, String suffix) {
        // 生成uuid
        String uuid = IdUtil.fastSimpleUUID();
        // 文件路径
        String path = DateUtil.format(new Date(), "yyyy/MM/dd") + "/" + uuid;
        if (StrUtil.isNotBlank(prefix)) {
            path = prefix + "/" + path;
        }
        return path + suffix;
    }

    /**
     * 获取私有URL链接
     *
     * @param objectKey 对象KEY
     * @param second    授权时间
     */
    public String getPrivateUrl(String objectKey, Integer second) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
                new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
                        .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
        return url.toString();
    }

    /**
     * 检查配置是否相同
     */
    public boolean checkPropertiesSame(StorageConfig properties) {
        return this.properties.equals(properties);
    }

    /**
     * 获取当前桶权限类型
     *
     * @return 当前桶权限类型code
     */
    public AccessPolicyType getAccessPolicy() {
        return AccessPolicyType.getByType(properties.getAccessPolicy());
    }

    private static String getPolicy(String bucketName, PolicyType policyType) {
        StringBuilder builder = new StringBuilder();
        builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
        if (policyType == PolicyType.WRITE) {
            builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n");
        } else if (policyType == PolicyType.READ_WRITE) {
            builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n");
        } else {
            builder.append("\"s3:GetBucketLocation\"\n");
        }
        builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("\"\n},\n");
        if (policyType == PolicyType.READ) {
            builder.append(
                    "{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
            builder.append(bucketName);
            builder.append("\"\n},\n");
        }
        builder.append("{\n\"Action\": ");
        switch (policyType) {
            case WRITE:
                builder.append(
                        "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
                break;
            case READ_WRITE:
                builder.append(
                        "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
                break;
            default:
                builder.append("\"s3:GetObject\",\n");
                break;
        }
        builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
        return builder.toString();
    }


    /**
     * @param path 相对路径
     * @return 文件内容
     * @throws Exception
     * @description 获取文件内容
     */
    public InputStream getContent(String path) throws Exception {
        return getObjectContent(path);
    }


    public String getPreSignedAccessUrl(String path) {
        try {
            return getPreSignedAccessUrl(path, DateField.MINUTE, 30);
        } catch (Exception e) {
            log.error("获取文件访问地址异常,path={}", path, e);
            throw new RuntimeException(String.format("获取文件访问地址异常,path=[%s]:[%s]", path, e.getMessage()));
        }
    }

    /**
     * 获取私有URL链接
     *
     * @param objectKey 对象KEY
     */
    public String getPreSignedAccessUrl(String objectKey, DateField dateField, Integer time) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
                new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
                        .withExpiration(DateUtil.offset(DateUtil.date(), dateField, time));
        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
        return url.toString();
    }

}
